package handler

import (
	"fmt"
	"gitee.com/simple-set/simple.io/src/event"
	"gitee.com/simple-set/simple.io/src/process/codec"
	"gitee.com/simple-set/simple.io/src/process/socks"
	"gitee.com/simple-set/simple.proxy/src/config"
	"io"
	"log/slog"
)

type SocksConnectHandler struct {
	client *Client
}

func NewSocksConnectHandler() *SocksConnectHandler {
	client := NewClient()
	client.register(NewSocksConnectBackend())
	if config.Config.Socks.AclFilePath != "" {
		client.SetInterceptor(NewInterceptor(config.Config.Socks.AclFilePath))
	}
	return &SocksConnectHandler{client: client}
}

func (c *SocksConnectHandler) Connect(request *socks.Request, response *socks.Response) error {
	starterClient, err := c.client.DialTcp(request.GetDstAddrString())
	if err != nil {
		_ = response.Reply(socks.HostUnreachable)
		return err
	}
	defer func() {
		_ = starterClient.Close()
	}()

	if err = response.SetAddrAndPort(starterClient.Session().Socket.LocalAddr()); err != nil {
		_ = response.Reply(socks.ServerFailure)
		return err
	}
	if err = response.Reply(socks.SuccessReply); err != nil {
		return err
	}
	if _, err = starterClient.Request(request); err != nil {
		return err
	}
	return io.EOF
}

type SocksConnectBackend struct{}

func NewSocksConnectBackend() *SocksConnectBackend {
	return &SocksConnectBackend{}
}

func (s *SocksConnectBackend) Active(session *event.Session, value any) (any, error) {
	request := value.(*socks.Request)
	slog.Info(fmt.Sprintf("Socks.Connect %s -> %s[%s]", request.Session().Socket.RemoteAddr(), request.GetDstAddrString(), session.Socket.RemoteAddr()))
	go func() {
		_, _ = codec.NewReadByteBuf(session.Socket.Reader).WriteTo(request.Buff())
	}()
	return request.Buff().WriteTo(session.Socket.Writer)
}
